package com.szzg10.webservice.chapter4;

import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.xml.namespace.NamespaceContext;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.Provider;
import javax.xml.ws.Service.Mode;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.http.HTTPBinding;
import javax.xml.ws.http.HTTPException;
import javax.xml.ws.BindingType;
import javax.xml.ws.ServiceMode;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.WebServiceProvider;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

@WebServiceProvider
@ServiceMode(value = Mode.MESSAGE)
@BindingType(value = HTTPBinding.HTTP_BINDING)
public class RestfulTeams implements Provider<Source> {

	private static final String fileName = "teams.ser";
	private static final String post_put_key = "cargo";
	private static final String strDirectory = "d:/temp";
	
	private byte[] teams_bytes;
	private List<Team> teams;
	private Map<String, Team> teams_map;
	
	@Resource
	private WebServiceContext webServiceContext;
	
	public RestfulTeams() {
		read_teams_from_file();
		deserialize();
	}
	
	@Override
	public Source invoke(Source source) {
		
		MessageContext ctx = webServiceContext.getMessageContext();
		
		String method = (String)ctx.get(MessageContext.HTTP_REQUEST_METHOD);
		if(method.equalsIgnoreCase("get")) {
			return doGet(ctx);
		}
		else if(method.equalsIgnoreCase("post")) {
			return doPost(ctx);
		}
		else if(method.equalsIgnoreCase("put")) {
			return doPut(ctx);
		}
		else if(method.equalsIgnoreCase("delete")) {
			return doDelete(ctx);
		}
		else {
			throw new HTTPException(405);
		}
	}
	
	private void read_teams_from_file() {
		
		String filePath = strDirectory + "/" + fileName;
		File file = new File(filePath);
		
		int length = (int)file.length();
		teams_bytes = new byte[length];
		
		try {
			FileInputStream in = new FileInputStream(file);
			in.read(teams_bytes);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private void deserialize() {
		
		ByteArrayInputStream in = new ByteArrayInputStream(teams_bytes);
		
		XMLDecoder xmlDecoder = new XMLDecoder(in);
		teams = (List<Team>)xmlDecoder.readObject();
		
		teams_map = new HashMap<String, Team>();
		for(Team team: teams) {
			teams_map.put(team.getName(), team);
		}
		
	}
	
	private Source doGet(MessageContext ctx) {
		
		String queryString = (String)ctx.get(MessageContext.QUERY_STRING);
		if(queryString == null) {
			ByteArrayInputStream in = new ByteArrayInputStream(teams_bytes);
			return new StreamSource(in);
		}
		else {
			String value = getValueFromQuery(queryString);
			Team team = teams_map.get(value);
			
			ByteArrayInputStream in = encoder_to_stream(team);
			return new StreamSource(in);
		}
	}
	
	private Source doPost(MessageContext ctx) {
		
		Map<String, List> mapHeaders = (Map<String, List>)ctx.get(MessageContext.HTTP_REQUEST_HEADERS);
		List<String> listPosts = mapHeaders.get(post_put_key);
		
		String xml = "";
		for(String element: listPosts) {
			xml += element;
		}
		
		ByteArrayInputStream in = new ByteArrayInputStream(xml.getBytes());
		
		DOMResult domResult = new DOMResult();
		try {
			Transformer transformer = TransformerFactory.newInstance().newTransformer();
			transformer.transform(new StreamSource(in), domResult);
			
			final String namespaceURI = "create_team";
			XPath xPath = XPathFactory.newInstance().newXPath();
			xPath.setNamespaceContext(new NamespaceContext() {
				
				@Override
				public Iterator getPrefixes(String namespaceURI) {
					return null;
				}
				
				@Override
				public String getPrefix(String namespaceURI) {
					return null;
				}
				
				@Override
				public String getNamespaceURI(String prefix) {
					return namespaceURI;
				}
			});
			
			String teamName = xPath.evaluate("/create_team/name", domResult.getNode());
			
			NodeList players = (NodeList)xPath.evaluate("/create_team/player", domResult.getNode(), XPathConstants.NODESET);
			
			List<Player> team_players = new ArrayList<Player>();
			for(int i = 0; i < players.getLength(); i++) {
				Node node = players.item(i);
				String name = node.getTextContent();
				
				Player player = new Player(name);
				team_players.add(player);
			}
			
			Team team = new Team(teamName, team_players);
			teams.add(team);
			teams_map.put(teamName, team);
			
			serialize();
			
			ByteArrayInputStream encoder_to_stream = encoder_to_stream("Team " + teamName + " created.");
			return new StreamSource(encoder_to_stream);
		} catch (TransformerConfigurationException e) {
			e.printStackTrace();
			throw new HTTPException(500);
		} catch (TransformerFactoryConfigurationError e) {
			e.printStackTrace();
			throw new HTTPException(500);
		} catch (TransformerException e) {
			e.printStackTrace();
			throw new HTTPException(500);
		} catch (XPathExpressionException e) {
			e.printStackTrace();
			throw new HTTPException(500);
		}
	}
	
	private StreamSource doPut(MessageContext ctx) {
		
		String queryString = (String)ctx.get(MessageContext.QUERY_STRING);
		String[] split = queryString.split("&");
		
		String oldName = getValueFromQuery(split[0]);
		String newName = getValueFromQuery(split[1]);
		
		Team team = teams_map.get(oldName);
		team.setName(newName);
		teams_map.put(newName, team);
		
		serialize();
		
		ByteArrayInputStream in = encoder_to_stream("Team " + oldName + " changed to " + newName);
		return new StreamSource(in);
		
	}
	
	public StreamSource doDelete(MessageContext ctx) {
		
		String query_string = (String)ctx.get(MessageContext.QUERY_STRING);
		String value = getValueFromQuery(query_string);
		
		Team team = teams_map.get(value);
		teams_map.remove(value);
		teams.remove(team);
		
		serialize();
		
		ByteArrayInputStream in = encoder_to_stream("Team " + value + " deleted.");
		return new StreamSource(in);
	}
	
	private String getValueFromQuery(String queryString) {
		
		String[] split = queryString.split("=");
		String value = split[1];
		
		return value;
	}
	
	private ByteArrayInputStream encoder_to_stream(Object object) {
		
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		
		XMLEncoder xmlEncoder = new XMLEncoder(out);
		xmlEncoder.writeObject(object);
		xmlEncoder.close();
		
		return new ByteArrayInputStream(out.toByteArray());
	}
	
	private void serialize() {
		
		File file = new File(strDirectory + "/" + fileName);
		BufferedOutputStream out;
		try {
			out = new BufferedOutputStream(new FileOutputStream(file));
			
			XMLEncoder xmlEncoder = new XMLEncoder(out);
			xmlEncoder.writeObject(teams);
			xmlEncoder.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
	
}
